package com.songshan.config;

import com.alibaba.druid.pool.DruidDataSource;
import com.github.pagehelper.PageInterceptor;
import com.songshan.interceptor.PermissionsInterceptor;
import java.util.List;
import java.util.Properties;
import javax.sql.DataSource;
import org.hibernate.validator.HibernateValidator;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.validation.Validator;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import org.springframework.web.multipart.commons.CommonsMultipartResolver;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.view.InternalResourceViewResolver;

/**
 * @author feb
 * @date 2024/6/18
 * @Description: MVC配置类，实现WebMvcConfigurer接口
 * @Configuration：标注为配置类，可以被Spring容器扫描
 * @EnableWebMvc：启用MVC：映射器、解析器、静态资源、转换器等，相当于xml配置中的<mvc:annotation-driven/>，且两者只能保留一个
 * @EnableAspectJAutoProxy：开启AOP的@Aspect等注解支持，相当于xml配置文件中的<aop:aspectj-autoproxy />
 * @EnableTransactionManagement：启用注解驱动的事务管理，在需要用到事务的方法上使用@Transactional注解开启事务，相当于xml配置文件中的<mvc:annotation-driven/>
 * @PropertySource：指定资源文件读取的位置，相当于xml配置文件中的<context:property-placeholder location="classpath:application.properties"/>
 * @MapperScan：将指定包下的Mapper注入到Spring的容器中，相当于xml配置文件中的MapperScannerConfigurer.basePackage配置
 */
@Configuration
@EnableWebMvc
@EnableAspectJAutoProxy
@EnableTransactionManagement
@PropertySource("classpath:application.properties")
@MapperScan(basePackages = "com.songshan.dao")
public class WebConfig implements WebMvcConfigurer {

  private Logger log = LoggerFactory.getLogger(this.getClass());

  /**
   * 获取application.properties文件中mysql.driver的值
   */
  @Value("${mysql.driver}")
  private String driver;
  @Value("${mysql.url}")
  private String url;
  @Value("${mysql.username}")
  private String username;
  @Value("${mysql.password}")
  private String password;
  @Value("${resource.uri}")
  private String resourceUri;
  @Value("${resource.path}")
  private String resourcePath;

  /**
   * 注入自定义权限校验拦截器
   */
  @Autowired
  private PermissionsInterceptor permissionsInterceptor;

  /**
   * @author feb
   * @date 2024/6/18
   * @Description: 注册MVC拦截器 将自定义权限校验拦截器注册到MVC拦截器列表 拦截所有.do后缀的接口，不拦截login.do接口
   */
  @Override
  public void addInterceptors(InterceptorRegistry registry) {
    registry.addInterceptor(permissionsInterceptor)
        .addPathPatterns("/**/*.do")
        .excludePathPatterns("/**/login.do","/menu/tree.do");
  }

  /**
   * @author feb
   * @date 2024/6/18
   * @Description: 注册MVC静态资源处理器 将静态资源请求地址映射到静态资源目录 静态资源请求地址=servletPath+ResourceHandler
   * 静态资源目录有两种：classpath:类资源目录，以及file:系统资源目录（必须以/结尾表示目录） 例子：/resource/static/photo/admin.png
   * /resource为web.xml的servlet-mapping配置中的url-pattern /static为静态资源处理器addResourceHandler配置
   * /photo/admin.png表示在addResourceLocations指定的目录下查找/photo/admin.png文件
   */
  @Override
  public void addResourceHandlers(ResourceHandlerRegistry registry) {
    registry.addResourceHandler(resourceUri)
        .addResourceLocations("file:"+resourcePath);
  }

  /**
   * @author feb
   * @date 2024/6/18
   * @Description: 配置文件上传解析器 拦截带有文件上传的请求，将文件解析成MultipartFile并封装在MultipartHttpServletRequest(继承了HttpServletRequest)中，传递给
   * Controller接口 setDefaultEncoding：设置请求参数解析的编码，有中文参数需要设置为UTF-8，否则会乱码
   * setMaxUploadSize：文件上传的最大值为20MB=20*1024*1024(单位为字节)
   */
  @Bean
  public CommonsMultipartResolver multipartResolver() {
    CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver();
    multipartResolver.setDefaultEncoding("UTF-8");
    multipartResolver.setMaxUploadSize(20 * 1024 * 1024);
    return multipartResolver;
  }

  /**
   * @author feb
   * @date 2024/6/18
   * @Description: 配置视图资源解析器 根据controller接口返回的视图名称找到对应的资源文件 setPrefix：配置资源文件的前缀 setSuffix：配置资源文件的后缀
   * 例子：controller接口返回main 资源文件=前缀+视图名称+后缀=/WEB-INF/views/main.html
   */
  @Bean
  public InternalResourceViewResolver viewResolver() {
    InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
    viewResolver.setPrefix("/WEB-INF/views/");
    viewResolver.setSuffix(".html");
    return viewResolver;
  }

  /**
   * @author feb
   * @date 2024/6/18
   * @Description: 添加消息转换器 MappingJackson2HttpMessageConverter将请求和响应转换成JSON格式的转换器
   * 请求参数使用@RequestBody标注，表示需要转换成JSON 响应结果使用@ResponseBody标注，表示需要转换成JSON
   */
  @Override
  public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
    MappingJackson2HttpMessageConverter jsonConverter = new MappingJackson2HttpMessageConverter();
    converters.add(jsonConverter);
  }

  /**
   * @author feb
   * @date 2024/6/18
   * @Description: 配置验证器 HibernateValidator：基于JSR303规范实现的验证框架 pojo类的属性使用@NotNull,
   * @NotEmpty等注解，实现数据校验 在controller接口的pojo类参数前加上@Valid注解，并用BindingResult参数接收数据校验结果
   */
  @Override
  public Validator getValidator() {
    LocalValidatorFactoryBean factoryBean = new LocalValidatorFactoryBean();
    factoryBean.setProviderClass(HibernateValidator.class);
    return factoryBean;
  }

  /**
   * @author feb
   * @date 2024/6/18
   * @Description: 配置数据源 根据application.properties文件中的数据库连接信息，初始化Druid数据源
   */
  @Bean
  public DruidDataSource dataSource() {
    DruidDataSource dataSource = new DruidDataSource();
    dataSource.setDriverClassName(driver);
    dataSource.setUrl(url);
    dataSource.setUsername(username);
    dataSource.setPassword(password);
    return dataSource;
  }

  /**
   * @author feb
   * @date 2024/6/18
   * @Description: 数据库连接会话工厂配置 根据Druid数据源初始化数据库连接会话工厂 setMapperLocations：指定mapper数据库sql映射文件的目录
   * helperDialect：指定数据库为mysql
   */
  @Bean
  public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource) {
    try {
      SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
      sqlSessionFactoryBean.setDataSource(dataSource);
      ResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver();
      sqlSessionFactoryBean
          .setMapperLocations(resourceResolver.getResources("classpath:mapper/*.xml"));
      PageInterceptor pageInterceptor = new PageInterceptor();
      Properties properties = new Properties();
      properties.setProperty("helperDialect", "mysql");
      pageInterceptor.setProperties(properties);
      sqlSessionFactoryBean.setPlugins(pageInterceptor);
      return sqlSessionFactoryBean;
    } catch (Exception e) {
      e.printStackTrace();
      throw new RuntimeException("数据库连接会话工厂初始化失败：" + e.getMessage());
    }
  }

  /**
   * @author feb
   * @date 2024/6/18
   * @Description: 配置事务管理
   */
  @Bean
  public DataSourceTransactionManager transactionManager(DataSource dataSource) {
    DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
    transactionManager.setDataSource(dataSource);
    return transactionManager;
  }
}
